﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using VetumaForSharePoint.Configuration;
using Microsoft.SharePoint;
using VetumaForSharePoint.Resources;
using System.Diagnostics;

namespace VetumaForSharePoint.DAL
{
    internal class DataAccess
    {
        // These are security question and answer required by active directory membership provider.
        // Because user is already authenticated with Vetuma, no real need to ask these from user.
        internal static string securityQuestion = "Are you sure?";
        internal static string securityQuestionAnswer = "Yes";

        /// <summary>
        /// Create account for the new user at specified user store.
        /// </summary>
        /// <param name="userName">The user name for the new user.</param>
        /// <param name="password">The password for the new user.</param>
        /// <param name="email">The email of the new user.</param>
        /// <param name="socialSecurityNumber">The social security number of the new user.</param>
        /// <returns>Boolean value indicating whether the user account was created successfully.</returns>
        internal static bool createAccount(string userName, string password, string email, string socialSecurityNumber)
        {
            MembershipUser newUser = null;

            VetumaForSharePointConfig config = VetumaForSharePointConfig.getConfig();

            MembershipProvider currentProvider = Membership.Providers[config.RegistrationConfig.Provider];

            MembershipCreateStatus status;

            // Create user account.
            newUser = currentProvider.CreateUser(userName, password, email, securityQuestion, securityQuestionAnswer, true, null, out status);

            if (status == MembershipCreateStatus.Success)
            {
                // User is successfully created so save user's social security number and 
                // if using AD(which does not support password recovery) encrypted password.
                if (config.RegistrationConfig.UserStore.ToLower() == "ad")
                {
                    saveVetumaUserData(userName, VFSHasher.computeHash(socialSecurityNumber), VFSHasher.Encrypt(password, config.RegistrationConfig.VFSKey));
                }
                else
                {
                    saveVetumaUserData(userName, VFSHasher.computeHash(socialSecurityNumber), null);
                }
                return true;
            }
            else if (status == MembershipCreateStatus.DuplicateUserName)
            {
                throw new VFSException(VetumaForSP.ResourceManager.GetString("UserNameExists"));
            }
            else if (status == MembershipCreateStatus.InvalidPassword)
            {
                throw new VFSException(VetumaForSP.ResourceManager.GetString("InvalidPassword"));
            }
            else if (status == MembershipCreateStatus.DuplicateEmail)
            {
                throw new VFSException(VetumaForSP.ResourceManager.GetString("EmailExists"));
            }
            else if (status == MembershipCreateStatus.InvalidEmail)
            {
                throw new VFSException(VetumaForSP.ResourceManager.GetString("InvalidEmail"));
            }

            return false;
        }

        /// <summary>
        /// Saves or updates user data to VfspList.
        /// </summary>
        /// <param name="hashedSocialSecurityNumber"></param>
        /// <param name="hashedPassword">Null or empty when using SQL-authentication.</param>
        internal static void saveVetumaUserData(string userName, string hashedSocialSecurityNumber, string hashedPassword)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite site = new SPSite(SPContext.Current.Site.ID))
                {
                    using (SPWeb web = site.RootWeb)
                    {
                        web.AllowUnsafeUpdates = true;

                        try
                        {
                            // Get vfsplist
                            SPList vfspList = web.Lists["vfspList"];

                            // Search user with user name
                            SPQuery query = new SPQuery();
                            query.Query = string.Format(@"<Where>
                                        <Eq>
                                             <FieldRef Name='VfspUserName' />
                                             <Value Type='Text'>{0}</Value>
                                       </Eq>
                                       </Where>", userName);

                            SPListItemCollection foundItems = vfspList.GetItems(query);

                            SPListItem userItem;

                            // If found, get list item. If not found, create new.
                            // If hashed password is empty, sql is used.
                            if (foundItems.Count == 1)
                            {
                                if (!string.IsNullOrEmpty(hashedPassword))
                                {
                                    userItem = foundItems[0];
                                    userItem["VfspPas"] = hashedPassword;

                                    userItem.SystemUpdate();
                                }
                            }
                            else
                            {
                                // No user item found, add new.
                                userItem = vfspList.Items.Add();
                                userItem["VfspUserName"] = userName;
                                userItem["VfspSoc"] = hashedSocialSecurityNumber;
                                if (!string.IsNullOrEmpty(hashedPassword))
                                {
                                    userItem["VfspPas"] = hashedPassword;
                                }

                                userItem.SystemUpdate();
                            }
                        }
                        catch (Exception ex)
                        {
                            logError(ex, EventLogEntryType.Error);
                        }
                        finally
                        {
                            web.AllowUnsafeUpdates = false;
                        }
                    };
                };
            });
        }

        /// <summary>
        /// Adds user to SharePoint groups that are specified in web.config.
        /// </summary>
        /// <param name="loginName">Login name of the user just created.</param>
        /// <param name="name">The name of the user just created.</param>
        /// <param name="email">Email of the user.</param>
        /// <returns>Boolean value which tells was user successfully added to SharePoint groups.</returns>
        internal static bool addUserToSharePointGroups(string loginName, string name, string email)
        {
            // This method is long because it is not recommended to send elevated privileges to other methods.

            bool ok = false;

            // Get groups where to add user
            SharePointGroups groups = VetumaForSharePointConfig.getConfig().SharePointGroups;

            // Run with elevated privileges because user is anonymous and anon user does not have appropriate rights.
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                // New spsite object must be declared when running with elevated privileges.
                using (SPSite site = new SPSite(SPContext.Current.Site.ID))
                {
                    // Get current web
                    using (SPWeb rootWeb = site.RootWeb)
                    {
                        // Allow unsafe updates to ensure that operation can be done.
                        rootWeb.AllowUnsafeUpdates = true;
                        rootWeb.Update();

                        try
                        {
                            // Add user to site users
                            rootWeb.SiteUsers.Add(loginName, email, name, VetumaForSP.ResourceManager.GetString("RegisteredWithVetuma"));

                            SPWebCollection allWebsAtSite = site.AllWebs;
                            int websCount = allWebsAtSite.Count;

                            // Loop each group
                            foreach (SharePointGroup spGroup in groups)
                            {
                                try
                                {
                                    if (!string.IsNullOrEmpty(spGroup.GroupName))
                                    {
                                        SPGroup group = null;

                                        // Let's try to find group from webs in site.
                                        for (int ii = 0; ii < websCount; ii++)
                                        {
                                            // By using using-block we don't need to use try-catch-finally with SPWeb which needs to be disposed.
                                            using (SPWeb siteWeb = allWebsAtSite[ii])
                                            {
                                                try
                                                {
                                                    // Get group where to add user.
                                                    // Throws SPException if group not found.
                                                    group = siteWeb.Groups[spGroup.GroupName];
                                                }
                                                catch (SPException)
                                                {
                                                    // Group not found from this web, let's move to next web.
                                                    continue;
                                                }

                                                if (group != null)
                                                {
                                                    try
                                                    {
                                                        // Allow unsafe updates to ensure that operation can be done.
                                                        siteWeb.AllowUnsafeUpdates = true;
                                                        siteWeb.Update();

                                                        // Add user to group
                                                        group.Users.Add(loginName, email, name, VetumaForSP.ResourceManager.GetString("RegisteredWithVetuma"));

                                                        // Update group
                                                        group.Update();
                                                        ok = true;
                                                    }
                                                    catch (Exception ex)
                                                    {
                                                        logError(ex, EventLogEntryType.Error);
                                                    }
                                                    finally
                                                    {
                                                        siteWeb.AllowUnsafeUpdates = false;
                                                        siteWeb.Update();
                                                    }

                                                    // Group found so let's break the search.
                                                    break;
                                                }
                                            };
                                        }

                                        if (group == null)
                                        {
                                            throw new NullReferenceException("Group '" + spGroup.GroupName + "' not found from this site collection.");
                                        }
                                    }
                                }
                                catch (Exception ex)
                                {
                                    logError(ex, EventLogEntryType.Error);
                                }
                            }
                        }
                        catch (Exception)
                        {
                            throw;
                        }
                        finally
                        {
                            rootWeb.AllowUnsafeUpdates = false;
                            rootWeb.Update();
                        }
                    };
                };
            });

            return ok;
        }

        /// <summary>
        /// Logs error.
        /// </summary>
        /// <param name="ex"></param>
        internal static void logError(Exception ex, EventLogEntryType entryType)
        {
            if (!ex.Message.Equals("Thread was being aborted."))
            {
                string message = ex.Message + " " + ex.StackTrace;

                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    EventLog.WriteEntry("Vetuma for SharePoint", message, entryType);
                });
            }
        }

        /// <summary>
        /// Searches user by social security number.
        /// </summary>
        /// <param name="socialSecurityNumber">User's social security number.</param>
        /// <returns>Found membership user. Null if not found.</returns>
        internal static MembershipUser findUser(string socialSecurityNumber)
        {
            // Get current provider.
            MembershipProvider currentProvider = Membership.Providers[VetumaForSharePointConfig.getConfig().RegistrationConfig.Provider];

            // Find user from VfspList.
            VetumaUser vUser = findVetumaUserBySocialSecurityNumber(socialSecurityNumber);

            if (vUser != null)
            {
                return currentProvider.GetUser(vUser.UserName, false);
            }
            else
            {
                return null;
            }
        }

        private static VetumaUser findVetumaUserBySocialSecurityNumber(string socialSecurityNumber)
        {
            // Hash social security number
            string hashedSocialSecurityNumber = VFSHasher.computeHash(socialSecurityNumber);

            VetumaUser vUser = null;

            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite site = new SPSite(SPContext.Current.Site.ID))
                {
                    using (SPWeb web = site.RootWeb)
                    {
                        SPList vfspList = web.Lists["vfspList"];

                        SPQuery query = new SPQuery();
                        query.Query = string.Format(@"<Where>
                                        <Eq>
                                             <FieldRef Name='VfspSoc' />
                                             <Value Type='Text'>{0}</Value>
                                       </Eq>
                                       </Where>", hashedSocialSecurityNumber);

                        SPListItemCollection foundItems = vfspList.GetItems(query);

                        if (foundItems.Count == 1)
                        {
                            SPListItem userItem = foundItems[0];

                            vUser = new VetumaUser();
                            vUser.UserName = userItem["VfspUserName"].ToString();
                            vUser.HashedSocialSecurityNumber = userItem["VfspSoc"].ToString();
                            if (userItem["VfspPas"] != null)
                            {
                                vUser.HashedPassword = userItem["VfspPas"].ToString();
                            }
                        }
                    };
                };
            });

            return vUser;
        }

        /// <summary>
        /// Find user details from VfspList by user name.
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        internal static VetumaUser findVetumaUserByUserName(string userName)
        {
            VetumaUser vUser = null;

            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite site = new SPSite(SPContext.Current.Site.ID))
                {
                    using (SPWeb web = site.RootWeb)
                    {
                        SPList vfspList = web.Lists["vfspList"];

                        SPQuery query = new SPQuery();
                        query.Query = string.Format(@"<Where>
                                        <Eq>
                                             <FieldRef Name='VfspUserName' />
                                             <Value Type='Text'>{0}</Value>
                                       </Eq>
                                       </Where>", userName);

                        SPListItemCollection foundItems = vfspList.GetItems(query);

                        if (foundItems.Count == 1)
                        {
                            SPListItem userItem = foundItems[0];

                            vUser = new VetumaUser();
                            vUser.UserName = userItem["VfspUserName"].ToString();
                            vUser.HashedSocialSecurityNumber = userItem["VfspSoc"].ToString();
                            if (userItem["VfspPas"] != null)
                            {
                                vUser.HashedPassword = userItem["VfspPas"].ToString();
                            }
                        }
                    };
                };
            });

            return vUser;
        }
    }
}
